<!DOCTYPE html>
<html>

  <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <title>安卓自定义View进阶-PathMeasure</title>
  <meta name="description" content="PathMeasure， 自定义View中2D绘图部分PathMeasure, 详细讲解PathMeasure的各种用法。">
  <meta name="author" content="GcsSloop">
  <meta name="keywords" content="Path, PathMeasure, PathMeasure详解, setPath, isClosed,  getPosTan, getLength, nextContour, getSegment,  getMatrix, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  <meta name="关键字" content="Path, PathMeasure, PathMeasure详解, setPath, isClosed,  getPosTan, getLength, nextContour, getSegment,  getMatrix, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  

  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="安卓自定义View进阶-PathMeasure">
  <meta name="twitter:description" content="PathMeasure， 自定义View中2D绘图部分PathMeasure, 详细讲解PathMeasure的各种用法。">
  <meta name="twitter:keywords" content="Path, PathMeasure, PathMeasure详解, setPath, isClosed,  getPosTan, getLength, nextContour, getSegment,  getMatrix, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  
  <meta property="og:type" content="article">
  <meta property="og:title" content="安卓自定义View进阶-PathMeasure">
  <meta property="og:description" content="PathMeasure， 自定义View中2D绘图部分PathMeasure, 详细讲解PathMeasure的各种用法。">
  <meta name="og:keywords" content="Path, PathMeasure, PathMeasure详解, setPath, isClosed,  getPosTan, getLength, nextContour, getSegment,  getMatrix, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">

  <meta name="theme-color" content="#343434">
  
  <link rel="icon" type="image/png" href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" />
  <link href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" rel="shortcut icon" type="image/png">
  
  <link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/css/main.css">

  <link rel="canonical" href="http://www.gcssloop.com/customview/Path_PathMeasure">
  <link rel="alternate" type="application/rss+xml" title="GcsSloop" href="http://www.gcssloop.com/feed.xml">
  
  <meta name="google-site-verification" content="Z_g58PkzRAyBMxkqrcDdWrTBK8oOWM-7rUHauhLNF2E" />
  <meta name="baidu-site-verification" content="kUtTXCKaZs" />
  <meta name="baidu-site-verification" content="6DuDv3aaJX" />
  
  <!--阅读次数统计-->
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"> </script>
  
  <!--Fuck Weixin and Baidu-->
  <meta http-equiv="Cache-Control" content="no-transform">
  <meta http-equiv=”Cache-Control” content=”no-siteapp” />
  <meta name="applicable-device" content="pc,mobile">
  <meta name="HandheldFriendly" content="true"/>

  <!-- Google Ad -->
  <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <script>
    (adsbygoogle = window.adsbygoogle || []).push({
      google_ad_client: "ca-pub-2767831356529421",
      enable_page_level_ads: true
    });
  </script>

</head>


  <body>

    <span class="mobile btn-mobile-menu">
        <i class="fa fa-list btn-mobile-menu__icon"></i>
        <i class="fa fa-angle-up btn-mobile-close__icon hidden"></i>
    </span>
    
    <header class="panel-cover panel-cover--collapsed" style="background-image: url('/assets/siteinfo/background-cover.jpg')">
  <div class="panel-main">

    <div class="panel-main__inner panel-inverted">
    <div class="panel-main__content">

        <a href="/#blog" title="前往 GcsSloop 的主页" class="blog-button"><img src="/assets/siteinfo/avatar.jpg" width="80" alt="GcsSloop logo" class="panel-cover__logo logo" /></a>
        <h1 class="panel-cover__title panel-title"><a href="/#blog" title="link to homepage for GcsSloop" class="blog-button">GcsSloop</a></h1>

        
        <span class="panel-cover__subtitle panel-subtitle">Just do IT later.</span>
        
        <hr class="panel-cover__divider" />
        <p class="panel-cover__description">嗨，我是 GcsSloop，一名来自2.5次元的魔法师，Android自定义View系列文章作者，非著名程序员。</p>
        <hr class="panel-cover__divider panel-cover__divider--secondary" />
        
        
        <p class="panel-cover__description">欢迎来到我的魔法世界!</p>
        
        
        <div class="navigation-wrapper">
          <div>
            <nav class="cover-navigation cover-navigation--primary">
              <ul class="navigation">
                <li class="navigation__item"><a href="/#blog" title="访问博客" class="blog-button">博客</a></li>
                
                  
                    <li class="navigation__item"><a href="https://github.com/GcsSloop" target="_blank" title="GcsSloop's GitHub">GitHub</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/timeline" title="博客目录">目录</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="https://xiaozhuanlan.com/u/GcsSloop" target="_blank" title="小专栏">专栏</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/category/customview" title="自定义View教程目录">自定义控件</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/friends" title="友链">友链</a></li>
                  
                  
                
              </ul>
            </nav>
          </div>
          
          <div><nav class="cover-navigation navigation--social">
  <ul class="navigation">

  
  <!-- Weibo -->
  <li class="navigation__item">
    <a href="http://weibo.com/GcsSloop" title="@GcsSloop 的微博" target="_blank">
      <i class='social fa fa-weibo'></i>
      <span class="label">Weibo</span>
    </a>
  </li>
  

  
  <!-- Github -->
  <li class="navigation__item">
    <a href="https://github.com/GcsSloop" title="@GcsSloop 的 Github" target="_blank">
      <i class='social fa fa-github'></i>
      <span class="label">Github</span>
    </a>
  </li>
  
  
  
  <!-- Twitter -->
  <li class="navigation__item">
    <a href="http://twitter.com/GcsSloop" title="@GcsSloop" target="_blank">
      <i class='social fa fa-twitter'></i>
      <span class="label">Twitter</span>
    </a>
  </li>
  

    

  

  
  <!-- RSS -->
  <li class="navigation__item">
    <a href="/feed.xml" rel="author" title="RSS" target="_blank">
      <i class='social fa fa-rss'></i>
      <span class="label">RSS</span>
    </a>
  </li>
  

  
  <!-- Email -->
  <li class="navigation__item">
    <a href="mailto:GcsSloop@gmail.com" title="发邮件给我">
      <i class='social fa fa-envelope'></i>
      <span class="label">Email</span>
    </a>
  </li>
  

  
  <!-- Copyright -->
  <li class="navigation__item">
    <a href="http://choosealicense.online" title="选择版权"  target="_blank">
      <i class="social fa fa-copyright"></i>
      <span class="label">版权</span>
    </a>
  </li>
  
  
  </ul>
</nav>
</div>
        </div>
      </div>
    </div>
    
    
    <div class="panel-cover--overlay cover-slate"></div>
    
  </div>
</header>


    <div class="content-wrapper">
        <div class="content-wrapper__inner">
            <article class="post-container post-container--single" itemscope itemtype="http://schema.org/BlogPosting">
  <header class="post-header">
    <div class="post-meta" style="font-size:.8em">
      <time datetime="2016-06-02 00:00:00 +0800" itemprop="datePublished" class="post-meta__date date">2016-06-02</time> &#8226; <span class="post-meta__tags tags">自定义View,PathMeasure</span> &#8226; View <span id="busuanzi_value_page_pv"></span> times.
</span>
    </div>
    <h1 class="post-title">安卓自定义View进阶-PathMeasure</h1>
  </header>

  <section class="post">
    <p>可以看到，在经过 
<a href="http://www.gcssloop.com/customview/Path_Basic">Path之基本操作</a>
<a href="http://www.gcssloop.com/customview/Path_Bezier">Path之贝塞尔曲线</a> 和 
<a href="http://www.gcssloop.com/customview/Path_Over">Path之完结篇</a> 后， Path中各类方法基本上都讲完了，表格中还没有讲解到到方法就是矩阵变换了，难道本篇终于要讲矩阵了？
非也，矩阵这一部分仍在后面单独讲解，本篇主要讲解 PathMeasure 这个类与 Path 的一些使用技巧。</p>

<blockquote>
  <p>PS：不要问我为什么不讲 PathEffect，因为这个方法在后面的Paint系列中。</p>
</blockquote>

<p>先放一个图镇楼，省的下面无聊的内容把你们都吓跑了Σ(￣。￣ﾉ)ﾉ</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-71758.gif?gcssloop" alt="" /></p>

<hr />

<h2 id="path--pathmeasure">Path &amp; PathMeasure</h2>

<p>顾名思义，PathMeasure是一个用来测量Path的类，主要有以下方法:</p>

<h3 id="构造方法">构造方法</h3>

<table>
  <thead>
    <tr>
      <th>方法名</th>
      <th>释义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>PathMeasure()</td>
      <td>创建一个空的PathMeasure</td>
    </tr>
    <tr>
      <td>PathMeasure(Path path, boolean forceClosed)</td>
      <td>创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。</td>
    </tr>
  </tbody>
</table>

<h3 id="公共方法">公共方法</h3>

<table>
  <thead>
    <tr>
      <th>返回值</th>
      <th>方法名</th>
      <th>释义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>void</td>
      <td>setPath(Path path, boolean forceClosed)</td>
      <td>关联一个Path</td>
    </tr>
    <tr>
      <td>boolean</td>
      <td>isClosed()</td>
      <td>是否闭合</td>
    </tr>
    <tr>
      <td>float</td>
      <td>getLength()</td>
      <td>获取Path的长度</td>
    </tr>
    <tr>
      <td>boolean</td>
      <td>nextContour()</td>
      <td>跳转到下一个轮廓</td>
    </tr>
    <tr>
      <td>boolean</td>
      <td>getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)</td>
      <td>截取片段</td>
    </tr>
    <tr>
      <td>boolean</td>
      <td>getPosTan(float distance, float[] pos, float[] tan)</td>
      <td>获取指定长度的位置坐标及该点切线值</td>
    </tr>
    <tr>
      <td>boolean</td>
      <td>getMatrix(float distance, Matrix matrix, int flags)</td>
      <td>获取指定长度的位置坐标及该点Matrix</td>
    </tr>
  </tbody>
</table>

<p>PathMeasure的方法也不多，接下来我们就逐一的讲解一下。</p>

<hr />

<h3 id="1构造函数">1.构造函数</h3>

<p>构造函数有两个。</p>

<p><strong>无参构造函数：</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code>  <span class="n">PathMeasure</span> <span class="o">()</span>
</code></pre>
</div>

<p>用这个构造函数可创建一个空的 PathMeasure，但是使用之前需要先调用 setPath 方法来与 Path 进行关联。被关联的 Path 必须是已经创建好的，如果关联之后 Path 内容进行了更改，则需要使用 setPath 方法重新关联。</p>

<p><strong>有参构造函数：</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code>  <span class="n">PathMeasure</span> <span class="o">(</span><span class="n">Path</span> <span class="n">path</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">forceClosed</span><span class="o">)</span>
</code></pre>
</div>

<p>用这个构造函数是创建一个 PathMeasure 并关联一个 Path， 其实和创建一个空的 PathMeasure 后调用 setPath 进行关联效果是一样的，同样，被关联的 Path 也必须是已经创建好的，如果关联之后 Path 内容进行了更改，则需要使用 setPath 方法重新关联。</p>

<p>该方法有两个参数，第一个参数自然就是被关联的 Path 了，第二个参数是用来确保 Path 闭合，如果设置为 true， 则不论之前Path是否闭合，都会自动闭合该 Path(如果Path可以闭合的话)。</p>

<p><strong>在这里有两点需要明确:</strong></p>

<blockquote>

  <ul>
    <li>
      <ol>
        <li>不论 forceClosed 设置为何种状态(true 或者 false)， 都不会影响原有Path的状态，<strong>即 Path 与 PathMeasure  关联之后，之前的的 Path 不会有任何改变。</strong></li>
      </ol>
    </li>
    <li>
      <ol>
        <li>forceClosed 的设置状态可能会影响测量结果，<strong>如果 Path 未闭合但在与 PathMeasure 关联的时候设置 forceClosed 为 true 时，测量结果可能会比 Path 实际长度稍长一点，获取到到是该 Path 闭合时的状态。</strong></li>
      </ol>
    </li>
  </ul>
</blockquote>

<p>下面我们用一个例子来验证一下：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mViewWidth</span><span class="o">/</span><span class="mi">2</span><span class="o">,</span><span class="n">mViewHeight</span><span class="o">/</span><span class="mi">2</span><span class="o">);</span>

<span class="n">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>

<span class="n">path</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span><span class="mi">200</span><span class="o">);</span>
<span class="n">path</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="mi">200</span><span class="o">,</span><span class="mi">200</span><span class="o">);</span>
<span class="n">path</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="mi">200</span><span class="o">,</span><span class="mi">0</span><span class="o">);</span>

<span class="n">PathMeasure</span> <span class="n">measure1</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span><span class="kc">false</span><span class="o">);</span>
<span class="n">PathMeasure</span> <span class="n">measure2</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span><span class="kc">true</span><span class="o">);</span>

<span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="s">"TAG"</span><span class="o">,</span> <span class="s">"forceClosed=false----&gt;"</span><span class="o">+</span><span class="n">measure1</span><span class="o">.</span><span class="na">getLength</span><span class="o">());</span>
<span class="n">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="s">"TAG"</span><span class="o">,</span> <span class="s">"forceClosed=true-----&gt;"</span><span class="o">+</span><span class="n">measure2</span><span class="o">.</span><span class="na">getLength</span><span class="o">());</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">path</span><span class="o">,</span><span class="n">mDeafultPaint</span><span class="o">);</span>
</code></pre>
</div>

<p>log如下:</p>

<div class="language-shell highlighter-rouge"><pre class="highlight"><code>com.gcssloop.canvas E/TAG: <span class="nv">forceClosed</span><span class="o">=</span><span class="nb">false</span>----&gt;600.0
com.gcssloop.canvas E/TAG: <span class="nv">forceClosed</span><span class="o">=</span><span class="nb">true</span>-----&gt;800.0
</code></pre>
</div>

<p>绘制在界面上的效果如下:</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071759.jpg?gcssloop" alt="" /></p>

<p>我们所创建的 Path 实际上是一个边长为 200 的正方形的三条边，通过上面的示例就能验证以上两个问题。</p>

<blockquote>

  <ul>
    <li>1.我们将 Path 与两个的 PathMeasure 进行关联，并给 forceClosed 设置了不同的状态，之后绘制再绘制出来的 Path 没有任何变化，所以与 Path 与 PathMeasure进行关联并不会影响 Path 状态。</li>
    <li>2.我们可以看到，设置 forceClosed 为 true 的方法比设置为 false 的方法测量出来的长度要长一点，这是由于 Path 没有闭合的缘故，多出来的距离正是 Path 最后一个点与最开始一个点之间点距离。<strong>forceClosed 为 false 测量的是当前 Path  状态的长度， forceClosed 为 true，则不论Path是否闭合测量的都是 Path 的闭合长度。</strong></li>
  </ul>
</blockquote>

<h3 id="2setpath-isclosed-和-getlength">2.setPath、 isClosed 和 getLength</h3>

<p>这三个方法都如字面意思一样，非常简单，这里就简单是叙述一下，不再过多讲解。</p>

<p>setPath 是 PathMeasure 与 Path 关联的重要方法，效果和 构造函数 中两个参数的作用是一样的。</p>

<p>isClosed 用于判断 Path 是否闭合，但是如果你在关联 Path 的时候设置 forceClosed 为 true 的话，这个方法的返回值则一定为true。</p>

<p>getLength 用于获取 Path 的总长度，在之前的测试中已经用过了。</p>

<h3 id="3getsegment">3.getSegment</h3>

<p>getSegment 用于获取Path的一个片段，方法如下：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kt">boolean</span> <span class="nf">getSegment</span> <span class="o">(</span><span class="kt">float</span> <span class="n">startD</span><span class="o">,</span> <span class="kt">float</span> <span class="n">stopD</span><span class="o">,</span> <span class="n">Path</span> <span class="n">dst</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">startWithMoveTo</span><span class="o">)</span>
</code></pre>
</div>

<p>方法各个参数释义：</p>

<table>
  <thead>
    <tr>
      <th>参数</th>
      <th>作用</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>返回值(boolean)</td>
      <td>判断截取是否成功</td>
      <td>true 表示截取成功，结果存入dst中，false 截取失败，不会改变dst中内容</td>
    </tr>
    <tr>
      <td>startD</td>
      <td>开始截取位置距离 Path 起点的长度</td>
      <td>取值范围: 0 &lt;= startD &lt; stopD &lt;= Path总长度</td>
    </tr>
    <tr>
      <td>stopD</td>
      <td>结束截取位置距离 Path 起点的长度</td>
      <td>取值范围: 0 &lt;= startD &lt; stopD &lt;= Path总长度</td>
    </tr>
    <tr>
      <td>dst</td>
      <td>截取的 Path 将会添加到 dst 中</td>
      <td>注意: 是添加，而不是替换</td>
    </tr>
    <tr>
      <td>startWithMoveTo</td>
      <td>起始点是否使用 moveTo</td>
      <td>用于保证截取的 Path 第一个点位置不变</td>
    </tr>
  </tbody>
</table>

<blockquote>

  <ul>
    <li>如果 startD、stopD 的数值不在取值范围 [0, getLength] 内，或者 startD == stopD 则返回值为 false，不会改变 dst 内容。</li>
    <li>如果在安卓4.4或者之前的版本，在默认开启硬件加速的情况下，更改 dst 的内容后可能绘制会出现问题，请关闭硬件加速或者给 dst  添加一个单个操作，例如: dst.rLineTo(0, 0)</li>
  </ul>
</blockquote>

<p>我们先看看这个方法如何使用：</p>

<p>我们创建了一个 Path， 并在其中添加了一个矩形，现在我们想截取矩形中的一部分，就是下图中红色的部分。</p>

<blockquote>
  <p>矩形边长400dp，起始点在左上角，顺时针</p>
</blockquote>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071801.jpg?gcssloop" alt="" /></p>

<p>代码:</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mViewWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mViewHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>          <span class="c1">// 平移坐标系</span>

<span class="n">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                     <span class="c1">// 创建Path并添加了一个矩形</span>
<span class="n">path</span><span class="o">.</span><span class="na">addRect</span><span class="o">(-</span><span class="mi">200</span><span class="o">,</span> <span class="o">-</span><span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="n">Path</span><span class="o">.</span><span class="na">Direction</span><span class="o">.</span><span class="na">CW</span><span class="o">);</span>

<span class="n">Path</span> <span class="n">dst</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                      <span class="c1">// 创建用于存储截取后内容的 Path</span>

<span class="n">PathMeasure</span> <span class="n">measure</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>         <span class="c1">// 将 Path 与 PathMeasure 关联</span>

<span class="c1">// 截取一部分存入dst中，并使用 moveTo 保持截取得到的 Path 第一个点的位置不变</span>
<span class="n">measure</span><span class="o">.</span><span class="na">getSegment</span><span class="o">(</span><span class="mi">200</span><span class="o">,</span> <span class="mi">600</span><span class="o">,</span> <span class="n">dst</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>                    

<span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">dst</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>                        <span class="c1">// 绘制 dst</span>
</code></pre>
</div>

<p>结果如下：</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071802.jpg?gcssloop" alt="" /></p>

<p>从上图可以看到我们成功到将需要到片段截取了出来，然而当 dst 中有内容时会怎样呢？</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mViewWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mViewHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>          <span class="c1">// 平移坐标系</span>

<span class="n">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                     <span class="c1">// 创建Path并添加了一个矩形</span>
<span class="n">path</span><span class="o">.</span><span class="na">addRect</span><span class="o">(-</span><span class="mi">200</span><span class="o">,</span> <span class="o">-</span><span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="n">Path</span><span class="o">.</span><span class="na">Direction</span><span class="o">.</span><span class="na">CW</span><span class="o">);</span>

<span class="n">Path</span> <span class="n">dst</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                      <span class="c1">// 创建用于存储截取后内容的 Path</span>
<span class="n">dst</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(-</span><span class="mi">300</span><span class="o">,</span> <span class="o">-</span><span class="mi">300</span><span class="o">);</span>                                     <span class="c1">// &lt;--- 在 dst 中添加一条线段</span>

<span class="n">PathMeasure</span> <span class="n">measure</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>         <span class="c1">// 将 Path 与 PathMeasure 关联</span>

<span class="n">measure</span><span class="o">.</span><span class="na">getSegment</span><span class="o">(</span><span class="mi">200</span><span class="o">,</span> <span class="mi">600</span><span class="o">,</span> <span class="n">dst</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>                   <span class="c1">// 截取一部分 并使用 moveTo 保持截取得到的 Path 第一个点的位置不变</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">dst</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>                        <span class="c1">// 绘制 Path</span>
</code></pre>
</div>

<p>结果如下:</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071803.jpg?gcssloop" alt="" /></p>

<p>从上面的示例可以看到 dst 中的线段保留了下来，可以得到结论：<strong>被截取的 Path 片段会添加到 dst 中，而不是替换 dst 中到内容。</strong></p>

<p>前面两个例子中 startWithMoveTo 均为 true， 如果设置为false会怎样呢?</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mViewWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mViewHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>          <span class="c1">// 平移坐标系</span>

<span class="n">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                     <span class="c1">// 创建Path并添加了一个矩形</span>
<span class="n">path</span><span class="o">.</span><span class="na">addRect</span><span class="o">(-</span><span class="mi">200</span><span class="o">,</span> <span class="o">-</span><span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="n">Path</span><span class="o">.</span><span class="na">Direction</span><span class="o">.</span><span class="na">CW</span><span class="o">);</span>

<span class="n">Path</span> <span class="n">dst</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                      <span class="c1">// 创建用于存储截取后内容的 Path</span>
<span class="n">dst</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(-</span><span class="mi">300</span><span class="o">,</span> <span class="o">-</span><span class="mi">300</span><span class="o">);</span>                                     <span class="c1">// 在 dst 中添加一条线段</span>

<span class="n">PathMeasure</span> <span class="n">measure</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>         <span class="c1">// 将 Path 与 PathMeasure 关联</span>

<span class="n">measure</span><span class="o">.</span><span class="na">getSegment</span><span class="o">(</span><span class="mi">200</span><span class="o">,</span> <span class="mi">600</span><span class="o">,</span> <span class="n">dst</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>                   <span class="c1">// &lt;--- 截取一部分 不使用 startMoveTo, 保持 dst 的连续性</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">dst</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>                        <span class="c1">// 绘制 Path</span>
</code></pre>
</div>

<p>结果如下：</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071804.jpg?gcssloop" alt="" /></p>

<p>从该示例我们又可以得到一条结论：<strong>如果 startWithMoveTo 为 true, 则被截取出来到Path片段保持原状，如果 startWithMoveTo 为 false，则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点，以保证 dst 的连续性。</strong></p>

<p>从而我们可以用以下规则来判断 startWithMoveTo 的取值：</p>

<table>
  <thead>
    <tr>
      <th>取值</th>
      <th>主要功用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>true</td>
      <td>保证截取得到的 Path 片段不会发生形变</td>
    </tr>
    <tr>
      <td>false</td>
      <td>保证存储截取片段的 Path(dst) 的连续性</td>
    </tr>
  </tbody>
</table>

<h3 id="4nextcontour">4.nextContour</h3>

<p>我们知道 Path 可以由多条曲线构成，但不论是 getLength , getSegment 或者是其它方法，都只会在其中第一条线段上运行，而这个 <code class="highlighter-rouge">nextContour</code> 就是用于跳转到下一条曲线到方法，<em>如果跳转成功，则返回 true， 如果跳转失败，则返回 false。</em></p>

<p>如下，我们创建了一个 Path 并使其中包含了两个闭合的曲线，内部的边长是200，外面的边长是400，现在我们使用 PathMeasure 分别测量两条曲线的总长度。</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-71807.jpg?gcssloop" alt="" /></p>

<p>代码：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mViewWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mViewHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>      <span class="c1">// 平移坐标系</span>

<span class="n">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>

<span class="n">path</span><span class="o">.</span><span class="na">addRect</span><span class="o">(-</span><span class="mi">100</span><span class="o">,</span> <span class="o">-</span><span class="mi">100</span><span class="o">,</span> <span class="mi">100</span><span class="o">,</span> <span class="mi">100</span><span class="o">,</span> <span class="n">Path</span><span class="o">.</span><span class="na">Direction</span><span class="o">.</span><span class="na">CW</span><span class="o">);</span>  <span class="c1">// 添加小矩形</span>
<span class="n">path</span><span class="o">.</span><span class="na">addRect</span><span class="o">(-</span><span class="mi">200</span><span class="o">,</span> <span class="o">-</span><span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="n">Path</span><span class="o">.</span><span class="na">Direction</span><span class="o">.</span><span class="na">CW</span><span class="o">);</span>  <span class="c1">// 添加大矩形</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">path</span><span class="o">,</span><span class="n">mDeafultPaint</span><span class="o">);</span>                    <span class="c1">// 绘制 Path</span>

<span class="n">PathMeasure</span> <span class="n">measure</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>     <span class="c1">// 将Path与PathMeasure关联</span>

<span class="kt">float</span> <span class="n">len1</span> <span class="o">=</span> <span class="n">measure</span><span class="o">.</span><span class="na">getLength</span><span class="o">();</span>                       <span class="c1">// 获得第一条路径的长度</span>

<span class="n">measure</span><span class="o">.</span><span class="na">nextContour</span><span class="o">();</span>                                  <span class="c1">// 跳转到下一条路径</span>

<span class="kt">float</span> <span class="n">len2</span> <span class="o">=</span> <span class="n">measure</span><span class="o">.</span><span class="na">getLength</span><span class="o">();</span>                       <span class="c1">// 获得第二条路径的长度</span>

<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"LEN"</span><span class="o">,</span><span class="s">"len1="</span><span class="o">+</span><span class="n">len1</span><span class="o">);</span>                              <span class="c1">// 输出两条路径的长度</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"LEN"</span><span class="o">,</span><span class="s">"len2="</span><span class="o">+</span><span class="n">len2</span><span class="o">);</span>
</code></pre>
</div>

<p>log输出结果:</p>

<div class="language-shell highlighter-rouge"><pre class="highlight"><code>com.gcssloop.canvas I/LEN: <span class="nv">len1</span><span class="o">=</span>800.0
com.gcssloop.canvas I/LEN: <span class="nv">len2</span><span class="o">=</span>1600.0
</code></pre>
</div>

<p>通过测试，我们可以得到以下内容：</p>

<ul>
  <li>1.曲线的顺序与 Path 中添加的顺序有关。</li>
  <li>2.getLength 获取到到是当前一条曲线分长度，而不是整个 Path 的长度。</li>
  <li>3.getLength 等方法是针对当前的曲线(其它方法请自行验证)。</li>
</ul>

<h4 id="5getpostan">5.getPosTan</h4>

<p>这个方法是用于得到路径上某一长度的位置以及该位置的正切值：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kt">boolean</span> <span class="nf">getPosTan</span> <span class="o">(</span><span class="kt">float</span> <span class="n">distance</span><span class="o">,</span> <span class="kt">float</span><span class="o">[]</span> <span class="n">pos</span><span class="o">,</span> <span class="kt">float</span><span class="o">[]</span> <span class="n">tan</span><span class="o">)</span>
</code></pre>
</div>

<p>方法各个参数释义：</p>

<table>
  <thead>
    <tr>
      <th>参数</th>
      <th>作用</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>返回值(boolean)</td>
      <td>判断获取是否成功</td>
      <td>true表示成功，数据会存入 pos 和 tan 中，<br />false 表示失败，pos 和 tan 不会改变</td>
    </tr>
    <tr>
      <td>distance</td>
      <td>距离 Path 起点的长度</td>
      <td>取值范围: 0 &lt;= distance &lt;= getLength</td>
    </tr>
    <tr>
      <td>pos</td>
      <td>该点的坐标值</td>
      <td>当前点在画布上的位置，有两个数值，分别为x，y坐标。</td>
    </tr>
    <tr>
      <td>tan</td>
      <td>该点的正切值</td>
      <td>当前点在曲线上的方向，使用  Math.atan2(tan[1], tan[0]) 获取到正切角的弧度值。</td>
    </tr>
  </tbody>
</table>

<p>这个方法也不难理解，除了其中 <code class="highlighter-rouge">tan</code> 这个东东，这个东西是干什么的呢？</p>

<p><code class="highlighter-rouge">tan</code> 是用来判断 Path 上趋势的，即在这个位置上曲线的走向，请看下图示例，注意箭头的方向:</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071808.gif?gcssloop" alt="" /></p>

<p><strong><a href="http://ww1.sinaimg.cn/large/005Xtdi2jw1f4gam21ktoj3069069jre.jpg">点击这里下载箭头图片</a></strong></p>

<p>可以看到 上图中箭头在沿着 Path 运动时，方向始终与 Path 走向保持一致，保持方向主要就是依靠 <code class="highlighter-rouge">tan</code> 。</p>

<p>下面我们来看看代码是如何实现的，首先我们需要定义几个必要的变量:</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">float</span> <span class="n">currentValue</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>     <span class="c1">// 用于纪录当前的位置,取值范围[0,1]映射Path的整个长度</span>

<span class="kd">private</span> <span class="kt">float</span><span class="o">[]</span> <span class="n">pos</span><span class="o">;</span>                <span class="c1">// 当前点的实际位置</span>
<span class="kd">private</span> <span class="kt">float</span><span class="o">[]</span> <span class="n">tan</span><span class="o">;</span>                <span class="c1">// 当前点的tangent值,用于计算图片所需旋转的角度</span>
<span class="kd">private</span> <span class="n">Bitmap</span> <span class="n">mBitmap</span><span class="o">;</span>             <span class="c1">// 箭头图片</span>
<span class="kd">private</span> <span class="n">Matrix</span> <span class="n">mMatrix</span><span class="o">;</span>             <span class="c1">// 矩阵,用于对图片进行一些操作</span>
</code></pre>
</div>

<p>初始化这些变量(在构造函数中调用这个方法):</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">pos</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">float</span><span class="o">[</span><span class="mi">2</span><span class="o">];</span>
    <span class="n">tan</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">float</span><span class="o">[</span><span class="mi">2</span><span class="o">];</span>
    <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">Options</span> <span class="n">options</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">Options</span><span class="o">();</span>
    <span class="n">options</span><span class="o">.</span><span class="na">inSampleSize</span> <span class="o">=</span> <span class="mi">2</span><span class="o">;</span>       <span class="c1">// 缩放图片</span>
    <span class="n">mBitmap</span> <span class="o">=</span> <span class="n">BitmapFactory</span><span class="o">.</span><span class="na">decodeResource</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">(),</span> <span class="n">R</span><span class="o">.</span><span class="na">drawable</span><span class="o">.</span><span class="na">arrow</span><span class="o">,</span> <span class="n">options</span><span class="o">);</span>
    <span class="n">mMatrix</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Matrix</span><span class="o">();</span>
<span class="o">}</span>
</code></pre>
</div>

<p>具体绘制:</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code>
<span class="n">canvas</span><span class="o">.</span><span class="na">translate</span><span class="o">(</span><span class="n">mViewWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mViewHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>      <span class="c1">// 平移坐标系</span>

<span class="n">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                 <span class="c1">// 创建 Path</span>

<span class="n">path</span><span class="o">.</span><span class="na">addCircle</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="n">Path</span><span class="o">.</span><span class="na">Direction</span><span class="o">.</span><span class="na">CW</span><span class="o">);</span>           <span class="c1">// 添加一个圆形</span>

<span class="n">PathMeasure</span> <span class="n">measure</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>     <span class="c1">// 创建 PathMeasure</span>

<span class="n">currentValue</span> <span class="o">+=</span> <span class="mf">0.005</span><span class="o">;</span>                                  <span class="c1">// 计算当前的位置在总长度上的比例[0,1]</span>
<span class="k">if</span> <span class="o">(</span><span class="n">currentValue</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
  <span class="n">currentValue</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>

<span class="n">measure</span><span class="o">.</span><span class="na">getPosTan</span><span class="o">(</span><span class="n">measure</span><span class="o">.</span><span class="na">getLength</span><span class="o">()</span> <span class="o">*</span> <span class="n">currentValue</span><span class="o">,</span> <span class="n">pos</span><span class="o">,</span> <span class="n">tan</span><span class="o">);</span>        <span class="c1">// 获取当前位置的坐标以及趋势</span>

<span class="n">mMatrix</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>                                                        <span class="c1">// 重置Matrix</span>
<span class="kt">float</span> <span class="n">degrees</span> <span class="o">=</span> <span class="o">(</span><span class="kt">float</span><span class="o">)</span> <span class="o">(</span><span class="n">Math</span><span class="o">.</span><span class="na">atan2</span><span class="o">(</span><span class="n">tan</span><span class="o">[</span><span class="mi">1</span><span class="o">],</span> <span class="n">tan</span><span class="o">[</span><span class="mi">0</span><span class="o">])</span> <span class="o">*</span> <span class="mf">180.0</span> <span class="o">/</span> <span class="n">Math</span><span class="o">.</span><span class="na">PI</span><span class="o">);</span> <span class="c1">// 计算图片旋转角度</span>

<span class="n">mMatrix</span><span class="o">.</span><span class="na">postRotate</span><span class="o">(</span><span class="n">degrees</span><span class="o">,</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getWidth</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getHeight</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>   <span class="c1">// 旋转图片</span>
<span class="n">mMatrix</span><span class="o">.</span><span class="na">postTranslate</span><span class="o">(</span><span class="n">pos</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">-</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getWidth</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="n">pos</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">-</span> <span class="n">mBitmap</span><span class="o">.</span><span class="na">getHeight</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>   <span class="c1">// 将图片绘制中心调整到与当前点重合</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>                                   <span class="c1">// 绘制 Path</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawBitmap</span><span class="o">(</span><span class="n">mBitmap</span><span class="o">,</span> <span class="n">mMatrix</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>                     <span class="c1">// 绘制箭头</span>

<span class="n">invalidate</span><span class="o">();</span>                                                           <span class="c1">// 重绘页面</span>
</code></pre>
</div>

<p><strong>核心要点:</strong></p>

<blockquote>

  <ul>
    <li>1.<strong>通过 <code class="highlighter-rouge">tan</code> 得值计算出图片旋转的角度</strong>，tan 是 tangent 的缩写，即中学中常见的正切， 其中tan[0]是邻边边长，tan[1]是对边边长，而Math中 <code class="highlighter-rouge">atan2</code> 方法是根据正切是数值计算出该角度的大小,得到的单位是弧度(取值范围是 -pi 到 pi)，所以上面又将弧度转为了角度。</li>
    <li>2.<strong>通过 <code class="highlighter-rouge">Matrix</code> 来设置图片对旋转角度和位移</strong>，这里使用的方法与前面讲解过对 canvas操作 有些类似，对于 <code class="highlighter-rouge">Matrix</code> 会在后面专一进行讲解，敬请期待。</li>
    <li>3.<strong>页面刷新</strong>，页面刷新此处是在 onDraw 里面调用了 invalidate 方法来保持界面不断刷新，但并不提倡这么做，正确对做法应该是使用 线程 或者 ValueAnimator 来控制界面的刷新，关于控制页面刷新这一部分会在后续的 动画部分 详细讲解，同样敬请期待。</li>
  </ul>
</blockquote>

<p>关于<code class="highlighter-rouge">tan</code>这个参数有很多魔法师不理解，特此拉出来详述一下，<code class="highlighter-rouge">tan</code> 在数学中被称为正切，在直角三角形中，一个锐角的<strong>正切</strong>定义为它的对边(Opposite side)与邻边(Adjacent side)的比值(来自维基百科)：</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071809.jpg?gcssloop" alt="" /></p>

<p>我们此处用 <code class="highlighter-rouge">tan</code> 来描述 Path 上某一点的切线方向，<strong>主要用了两个数值 tan[0] 和 tan[1] 来描述这个切线的方向(切线方向与x轴夹角)</strong> ，看上面公式可知 <code class="highlighter-rouge">tan</code> 既可以用 <code class="highlighter-rouge">对边／邻边</code> 来表述，也可以用 <code class="highlighter-rouge">sin／cos</code> 来表述，此处用两种理解方式均可以(<strong>注意下面等价关系</strong>):</p>

<blockquote>
  <p><strong>tan[0] = cos = 邻边(单位圆x坐标)</strong> <br />
<strong>tan[1] = sin = 对边(单位圆y坐标)</strong></p>
</blockquote>

<p><strong>以 <code class="highlighter-rouge">sin／cos</code>理解:</strong></p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071810.jpg?gcssloop" alt="" /></p>

<p>在圆上最右侧点的切线方向向下(动图中小飞机朝向和切线朝向一致)，切线角度为90度.<br />
sin90 = 1，cos90 = 0<br />
tan[0] = cos = 0<br />
tan[1] = sin = 1</p>

<p><strong>以 <code class="highlighter-rouge">对边／邻边</code> 理解(单位圆上坐标):</strong></p>

<p>按照这种理解方式需要借助一个单位圆，单位圆上任意一点到圆心到距离均为 1，以下图30度为例：</p>

<p><img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f8x0h7l7epj30k00k0juo.jpg" width="500" /></p>

<p>tan30 = 对边／邻边 = AB／OA = B点y坐标／B点x坐标</p>

<blockquote>
  <p><strong>另外根据单位圆性质同样可以证得:</strong><br />
sin30 = 对边／斜边 = AB／OB = AB = B点y坐标 (单位圆边上任意一点距离圆心距离均为1，故OB = 1)<br />
cos30 = 邻边／斜边 = OA／OB = OA = B点x坐标</p>

  <p><strong>化为通用公式即为:</strong><br />
sin = 该角度在单位圆上对应点的y坐标  <br />
cos = 该角度在单位圆上对应点的x坐标</p>

  <p>即 tan = sin／cos = y／x<br />
tan[0] = x<br />
tan[1] = y</p>

  <p>另外注意，这个单位圆与小飞机路径没有半毛钱关系，例如上一个例子中的90度切线，不要在单位圆上找对应位置，<strong>要找对应角度的位置，90度对应的位置是(0，1)</strong>，所以:<br />
tan[0] = x = 0<br />
tan[1] = y = 1</p>

  <p>其实绕来绕去全是等价的  (╯°Д°)╯︵ ┻━┻</p>
</blockquote>

<p><strong>PS: 使用 Math.atan2(tan[1], tan[0]) 将 <code class="highlighter-rouge">tan</code> 转化为角(单位为弧度)的时候要注意参数顺序。</strong></p>

<h3 id="6getmatrix">6.getMatrix</h3>

<p>这个方法是用于得到路径上某一长度的位置以及该位置的正切值的矩阵：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kt">boolean</span> <span class="nf">getMatrix</span> <span class="o">(</span><span class="kt">float</span> <span class="n">distance</span><span class="o">,</span> <span class="n">Matrix</span> <span class="n">matrix</span><span class="o">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="o">)</span>
</code></pre>
</div>

<p>方法各个参数释义：</p>

<table>
  <thead>
    <tr>
      <th>参数</th>
      <th>作用</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>返回值(boolean)</td>
      <td>判断获取是否成功</td>
      <td>true表示成功，数据会存入matrix中，false 失败，matrix内容不会改变</td>
    </tr>
    <tr>
      <td>distance</td>
      <td>距离 Path 起点的长度</td>
      <td>取值范围: 0 &lt;= distance &lt;= getLength</td>
    </tr>
    <tr>
      <td>matrix</td>
      <td>根据 falgs 封装好的matrix</td>
      <td>会根据 flags 的设置而存入不同的内容</td>
    </tr>
    <tr>
      <td>flags</td>
      <td>规定哪些内容会存入到matrix中</td>
      <td>可选择<br />POSITION_MATRIX_FLAG(位置) <br />ANGENT_MATRIX_FLAG(正切)</td>
    </tr>
  </tbody>
</table>

<p>其实这个方法就相当于我们在前一个例子中封装 <code class="highlighter-rouge">matrix</code> 的过程由 <code class="highlighter-rouge">getMatrix</code> 替我们做了，我们可以直接得到一个封装好到 <code class="highlighter-rouge">matrix</code>，岂不快哉。</p>

<p>但是我们看到最后到 <code class="highlighter-rouge">flags</code> 选项可以选择 <code class="highlighter-rouge">位置</code> 或者 <code class="highlighter-rouge">正切</code> ,如果我们两个选项都想选择怎么办？</p>

<p>如果两个选项都想选择，可以将两个选项之间用 <code class="highlighter-rouge">|</code> 连接起来，如下：</p>

<div class="highlighter-rouge"><pre class="highlight"><code>measure.getMatrix(distance, matrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
</code></pre>
</div>

<p>我们可以将上面都例子中 <code class="highlighter-rouge">getPosTan</code> 替换为 <code class="highlighter-rouge">getMatrix</code>， 看看是不是会显得简单很多:</p>

<p>具体绘制:</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span>                                 <span class="c1">// 创建 Path</span>

<span class="n">path</span><span class="o">.</span><span class="na">addCircle</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">200</span><span class="o">,</span> <span class="n">Path</span><span class="o">.</span><span class="na">Direction</span><span class="o">.</span><span class="na">CW</span><span class="o">);</span>           <span class="c1">// 添加一个圆形</span>

<span class="n">PathMeasure</span> <span class="n">measure</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PathMeasure</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>     <span class="c1">// 创建 PathMeasure</span>

<span class="n">currentValue</span> <span class="o">+=</span> <span class="mf">0.005</span><span class="o">;</span>                                  <span class="c1">// 计算当前的位置在总长度上的比例[0,1]</span>
<span class="k">if</span> <span class="o">(</span><span class="n">currentValue</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">currentValue</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// 获取当前位置的坐标以及趋势的矩阵</span>
<span class="n">measure</span><span class="o">.</span><span class="na">getMatrix</span><span class="o">(</span><span class="n">measure</span><span class="o">.</span><span class="na">getLength</span><span class="o">()</span> <span class="o">*</span> <span class="n">currentValue</span><span class="o">,</span> <span class="n">mMatrix</span><span class="o">,</span> <span class="n">PathMeasure</span><span class="o">.</span><span class="na">TANGENT_MATRIX_FLAG</span> <span class="o">|</span> <span class="n">PathMeasure</span><span class="o">.</span><span class="na">POSITION_MATRIX_FLAG</span><span class="o">);</span>

<span class="n">mMatrix</span><span class="o">.</span><span class="na">preTranslate</span><span class="o">(-</span><span class="n">mBitmap</span><span class="o">.</span><span class="na">getWidth</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">,</span> <span class="o">-</span><span class="n">mBitmap</span><span class="o">.</span><span class="na">getHeight</span><span class="o">()</span> <span class="o">/</span> <span class="mi">2</span><span class="o">);</span>   <span class="c1">// &lt;-- 将图片绘制中心调整到与当前点重合(注意:此处是前乘pre)</span>

<span class="n">canvas</span><span class="o">.</span><span class="na">drawPath</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>                                   <span class="c1">// 绘制 Path</span>
<span class="n">canvas</span><span class="o">.</span><span class="na">drawBitmap</span><span class="o">(</span><span class="n">mBitmap</span><span class="o">,</span> <span class="n">mMatrix</span><span class="o">,</span> <span class="n">mDeafultPaint</span><span class="o">);</span>                     <span class="c1">// 绘制箭头</span>

<span class="n">invalidate</span><span class="o">();</span>                                                           <span class="c1">// 重绘页面</span>
</code></pre>
</div>

<blockquote>
  <p>由于此处代码运行结果与上面一样，便不再贴图片了，请参照上面一个示例的效果图。</p>
</blockquote>

<p>可以看到使用 getMatrix 方法的确可以节省一些代码，不过这里依旧需要注意一些内容:</p>

<blockquote>

  <ul>
    <li>1.对 <code class="highlighter-rouge">matrix</code> 的操作必须要在 <code class="highlighter-rouge">getMatrix</code> 之后进行，否则会被 <code class="highlighter-rouge">getMatrix</code> 重置而导致无效。</li>
    <li>2.矩阵对旋转角度默认为图片的左上角，我们此处需要使用 <code class="highlighter-rouge">preTranslate</code> 调整为图片中心。</li>
    <li>3.pre(矩阵前乘) 与 post(矩阵后乘) 的区别，此处请等待后续的文章或者自行搜索。</li>
  </ul>
</blockquote>

<hr />

<h2 id="path--svg">Path &amp; SVG</h2>

<p>我们知道，用Path可以创建出各种个样的图形，但如果图形过于复杂时，用代码写就不现实了，不仅麻烦，而且容易出错，所以在绘制复杂的图形时我们一般是将 SVG 图像转换为 Path。</p>

<p><strong>你说什么是 SVG?</strong></p>

<p>SVG 是一种矢量图，内部用的是 xml 格式化存储方式存储这操作和数据，你完全可以将 SVG 看作是 Path 的各项操作简化书写后的存储格式。</p>

<p>Path 和 SVG 结合通常能诞生出一些奇妙的东西，如下:</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071812.gif?gcssloop" alt="" />
<img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071813.gif?gcssloop" alt="" /></p>

<blockquote>

  <p><strong>该图片来自这个开源库 -&gt;<a href="https://github.com/geftimov/android-pathview">PathView</a></strong> <br />
<strong>SVG 转 Path 的解析可以用这个库 -&gt; <a href="https://bigbadaboom.github.io/androidsvg/">AndroidSVG</a></strong></p>
</blockquote>

<p>限于篇幅以及本人精力，这一部分就暂不详解了，感兴趣的可以直接看源码，或者搜索一些相关的解析文章。</p>

<hr />

<h2 id="path使用技巧">Path使用技巧</h2>

<p><strong>话说本篇文章的名字不是叫 玩出花样么？怎么只见前面啰啰嗦嗦的扯了一大堆不明所以的东西，花样在哪里？</strong></p>

<blockquote>

  <p><strong>前面的内容虽然啰嗦繁杂，但却是重中之重的基础，如果在修仙界，这叫根基，而下面讲述的内容的是招式，有了根基才能演化出千变万化的招式，而没有根基只学招式则是徒有其表，只能学一样会一样，很难适应千变万化的需求。</strong></p>
</blockquote>

<p>先放一个效果图，然后分析一下实现过程:</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-71758.gif?gcssloop" alt="" /></p>

<p>这是一个搜索的动效图，通过分析可以得到它应该有四种状态，分别如下:</p>

<table>
  <thead>
    <tr>
      <th>状态</th>
      <th>概述</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>初始状态</td>
      <td>初始状态，没有任何动效，只显示一个搜索标志 🔍</td>
    </tr>
    <tr>
      <td>准备搜索</td>
      <td>放大镜图标逐渐变化为一个点</td>
    </tr>
    <tr>
      <td>正在搜索</td>
      <td>围绕这一个圆环运动，并且线段长度会周期性变化</td>
    </tr>
    <tr>
      <td>准备结束</td>
      <td>从一个点逐渐变化成为放大镜图标</td>
    </tr>
  </tbody>
</table>

<p>这些状态是有序转换的，转换流程以及转换条件如下：</p>

<blockquote>
  <p>其中 <code class="highlighter-rouge">正在搜索</code> 这个状态持续时间长度是不确定的，在没有搜索完成前，应该一直处于搜索状态。</p>
</blockquote>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071817.jpg?gcssloop" alt="" /></p>

<p>简单的分析了其大致的流程之后，就到了制作的重点:对细节对把握。</p>

<h3 id="path-划分">Path 划分</h3>

<p>为了制作对方便，此处整个动效用了两个 Path， 一个是中间对放大镜， 另一个则是外侧的圆环,将两者全部画出来是这样子的。</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071818.jpg?gcssloop" alt="" /></p>

<p>其中 Path 的走向要把握好，如下(只是一个放大镜，并不是♂):</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-71819.jpg?gcssloop" alt="" /></p>

<p>其中圆形上面的点可以用 PathMeasure 测量，无需计算。</p>

<h3 id="动画状态与时间关联">动画状态与时间关联</h3>

<p>此处使用的是 ValueAnimator，它可以将一段时间映射到一段数值上，随着时间变化不断的更新数值，并且可以使用插值器开控制数值变化规律(此处使用的是默认插值器)。</p>

<blockquote>
  <p>PS: 本来不想提前暴露这个的，准备偷偷留到动画部分(｡-_-｡) 但实在是没有优雅的替代方案了。</p>
</blockquote>

<h3 id="具体绘制">具体绘制</h3>

<p>绘制部分是根据 当前状态以及从 ValueAnimator 获得的数值来截取 Path 中合适的部分绘制出来。</p>

<h3 id="最终效果">最终效果</h3>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071819.gif?gcssloop" alt="" /></p>

<h3 id="源码">源码</h3>

<p>上面的内容是为了帮助大家从把控全局流程以及理解某些细节的设计思路，而更多的内容都藏在代码中，代码总体也不算长，感兴趣的可以自己敲一遍。</p>

<h4 id="戳这里查看源码"><a href="https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/Code/SearchView.md">戳这里查看源码</a></h4>

<blockquote>
  <p>PS: 本代码仅作为示例使用，还有诸多不足，如 自定义属性，视图大小， 点击事件， 监听回调 等，并不适合直接使用，有需要的可以自行补足相关内容。</p>
</blockquote>

<h2 id="总结">总结</h2>

<p><strong>本文中虽然后面的内容看起来比较高大上一点，但前面”啰嗦”的废话才是真正的干货，把前面的东西学会了，后面的各种效果都能信手拈来，如果只研究后面的东西，则是取其形，而难以会其意。</strong></p>

<h4 id="ps-由于本人水平有限某些地方可能存在误解或不准确如果你对此有疑问可以提交issues进行反馈">PS: 由于本人水平有限，某些地方可能存在误解或不准确，如果你对此有疑问可以提交Issues进行反馈。</h4>

<h2 id="about">About</h2>

<p><a href="http://www.gcssloop.com/customview/CustomViewIndex">本系列相关文章</a></p>

<p>作者微博: <a href="http://weibo.com/GcsSloop">GcsSloop</a></p>

<h2 id="参考资料">参考资料</h2>
<p><a href="https://developer.android.com/reference/android/graphics/PathMeasure.html">PathMeasure</a><br />
<a href="https://bigbadaboom.github.io/androidsvg/">AndroidSVG</a><br />
<a href="https://github.com/geftimov/android-pathview">android-pathview</a><br />
<a href="http://blog.csdn.net/cquwentao/article/details/51436852">android Path 和 PathMeasure 进阶</a><br /></p>


    <hr>
  </section>
</article>

<!--广告-->

<!--
<div>
<a href="https://m.aliyun.com/act/team1111/?spm=5176.11533457.1089570.5.424777e3AF8WOJ&userCode=hn5smxtw#/" target="_blank"><img src="/assets/aliyun/1111-980-120.jpg" width="100%"></a>
</div>
-->
<!--捐赠晶石-->
<section class="contribute">
    <script type="text/javascript"> 
      function toggle() {
        var con = document.getElementById("contribute");
        if (con.style.display == "none") {
          con.style.display = "block";
        } else {
          con.style.display = "none";
        }
      }
    </script> 
    <blockquote style="background-color:#F5F5F5; padding: 10px 20px 20px 10px; margin:0px" >
      <h4> 如果你觉得我的文章对你有帮助的话，欢迎赞助一些服务器费用! </h4>
      <p></p>
      <a id=“btn-toggle-contribute” class="btn-contribute" onclick="toggle()" >¥ 点击赞助</a>
      <br>
      <div id="contribute" style="display:none;">
        <p align="center" >
        <img src="/assets/images/wechat.png" alt="微信">
        <img src="/assets/images/alipay.png" alt="支付宝">
        </p>
        <p align="left" >
          <b>感谢所有支持我的魔法师，所有支持过我的魔法师都可以通过微信(GcsSloop)联系我，获赠我的付费专栏！</b>
          <!--
          <a href="/contribute">点击这里查看捐赠者名单。</a>
          -->
        </p>
      </div>
    </blockquote>
</section>
<div>
  <h2>欢迎关注我的微信公众号</h2>
  <img src="/assets/images/banner.jpg" width="100%">
</div>

<!--阅读更多-->
<section class="read-more">
  
  
  <div class="read-more-item">
    <span class="read-more-item-dim">最近的文章</span>
    <h2 class="post-list__post-title post-title"><a href="/customview/Matrix_Basic" title="link to 安卓自定义View进阶-Matrix原理">安卓自定义View进阶-Matrix原理</a></h2>
    <p class="excerpt">本文内容偏向理论，和 画布操作 有重叠的部分，本文会让你更加深入的了解其中的原理。本篇的主角Matrix，是一个一直在后台默默工作的劳动模范，虽然我们所有看到View背后都有着Matrix的功劳...&hellip;</p>
    <div class="post-list__meta">
      <time datetime="2016-08-03 00:00:00 +0800" class="post-list__meta--date date">2016-08-03</time> &#8226; <span class="post-list__meta--tags tags">CustomView</span>
      <br/><br/>
      <a style="float:none; margin:0 auto;" class="btn-border-small" href=/customview/Matrix_Basic>继续阅读</a></div>
   </div>
   
   
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">更早的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/customview/Path_Over" title="link to 安卓自定义View进阶-Path之完结篇">安卓自定义View进阶-Path之完结篇</a></h2>
       <p class="excerpt">经历过前两篇 Path之基本操作 和 Path之贝塞尔曲线 的讲解，本篇终于进入Path的收尾篇，本篇结束后Path的大部分相关方法都已经讲解完了，但Path还有一些更有意思的玩法，应该会在后续...&hellip;</p>
       <div class="post-list__meta">
          <time datetime="2016-05-02 00:00:00 +0800" class="post-list__meta--date date">2016-05-02</time> &#8226; <span class="post-list__meta--tags tags">CustomView</span>
          <br/><br/>
          <a style="float:none; margin:0 auto;" class="btn-border-small" href=/customview/Path_Over>继续阅读</a>
       </div>
   </div>
   
</section>

<!--网易云跟帖-->
<!--
<div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
<script src="https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js"></script>
<script>
var cloudTieConfig = {
  url: document.location.href, 
  sourceId: "",
  productKey: "a85dba2840134721a7b69a15b2e0f217",
  target: "cloud-tie-wrapper"
};
var yunManualLoad = true;
Tie.loader("aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s", true);
</script>
-->

<style type="text/css">
.isso-comment > div.avatar {
    border: 0px;
    box-shadow: none;
    display: block;
    float: left;
    width: 7%;
    margin: 3px 15px 0 0;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input {
    border-radius: 6px;
    padding: 6px;
    padding-left: 16px;
    padding-right: 16px;
    border: 1px solid #CCC;
    background-color: #D58D44;
    cursor: pointer;
    outline: 0;
    color: #fff;
    size: 10;
    line-height: 1.4em;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:hover {
    background-color: #272822;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
    background-color: #986530;
}
</style>

<section id="isso-thread"></section>

<script data-isso="//47.52.58.34:1234/"
        data-isso-css="true"
        data-isso-lang="zh"
        data-isso-reply-to-self="false"
        data-isso-require-author="false"
        data-isso-require-email="false"
        data-isso-max-comments-top="10"
        data-isso-max-comments-nested="5"
        data-isso-reveal-on-click="5"
        data-isso-avatar="true"
        data-isso-avatar-bg="#f0f0f0"
        data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
        data-isso-vote="true"
        data-vote-levels=""
        src="//47.52.58.34:1234/js/embed.min.js">
        </script>

<!--
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
-->
<!-- OneV's Den -->
<!--
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client="ca-pub-3324997515191619"
     data-ad-slot="9170309685"
     data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
-->

            <section class="footer">
    <footer>
    	<span class="footer__copyright">本站点采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh" target="_blank">知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议</a></span>
        <span class="footer__copyright">本站由 <a href="http://www.GcsSloop.com">@GcsSloop</a> 创建，采用 <a href="https://github.com/GcsSloop/Gcs-Vno-Jekyll" target="_blank">Gcs-Vno-Jekyll</a> 作为主题。<span id="busuanzi_container_site_pv"> 总访问量 <span id="busuanzi_value_site_pv"></span> 次</span> - &copy; 2019</span>
        <span class="footer__sitemap, footer__copyright"><a href="http://www.gcssloop.com/sitemap.xml" target="_blank">Site Map</a>
        <a href="http://www.gcssloop.com/vip" target="_blank">vip</a></span>
    </footer>
</section>

        </div>
    </div>
    
    <script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>

<script type="text/javascript" src="/js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

<script type="text/javascript" src="/js/main.js"></script>

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-82493667-1', 'auto');
  ga('send', 'pageview');

</script>

    
  </body>

</html>
